//
//  CallbacksProxy.m
//  MacDemo
//
//  Created by FH on 2020/11/18.
//  Copyright © 2020 FH. All rights reserved.
//

#import "ObjCInvokeProxy.h"
#import <objc/runtime.h>
#import <objc/message.h>

typedef id (^ParseArgValueBlock)(void *, BOOL, NSString *);
typedef void (^BlockArg_V)(void *);
typedef void (^BlockArg_V_V)(void *, void *);
typedef void (^BlockArg_V_V_V)(void *, void *, void *);
typedef void (^BlockArg_V_V_V_V)(void *, void *, void *, void *);
typedef void (^BlockArg_V_V_V_V_V)(void *, void *, void *, void *, void *);
typedef void (^BlockArg_V_V_V_V_V_V)(void *, void *, void *, void *, void *, void *);

union int_cast {
    void * ptr;
    NSInteger value;
};

union uint_cast {
    void * ptr;
    NSUInteger value;
};

union bool_cast {
    void * ptr;
    BOOL value;
};

///////////////////////////////////////////////////

@interface ObjCInvokeProxy ()

@property (nonatomic, strong) ProxyCanRespondBlock canRespondBlock;
@property (nonatomic, strong) ProxySearchSignatureBlock searchSignatureBlock;
@property (nonatomic, strong) ProxyForwardInvocationBlock forwardInvocationBlock;

@end

@implementation ObjCInvokeProxy

+ (id)getObjcRefValueWithInvocation:(id)invocation index:(NSInteger)index forReturnValue:(BOOL)forReturnValue {
    NSInvocation *objcInvocation = (NSInvocation *)invocation;
    __unsafe_unretained NSObject *arg = nil;
    if (forReturnValue) {
        [objcInvocation getReturnValue:&arg];
    } else {
        [objcInvocation getArgument:&arg atIndex:index];
    }
    return arg;
}

- (id)createInstanceFromClass:(Class)cls {
    return [cls new];
}

- (instancetype)initWithCanRespondBlock:(ProxyCanRespondBlock _Nonnull)canRespondBlock
                   SearchSignatureBlock:(ProxySearchSignatureBlock _Nonnull)searchSignatureBlock
                 forwardInvocationBlock:(ProxyForwardInvocationBlock _Nonnull)forwardInvocationBlock {
    if (self = [super init]) {
        self.canRespondBlock = canRespondBlock;
        self.searchSignatureBlock = searchSignatureBlock;
        self.forwardInvocationBlock = forwardInvocationBlock;
    }
    return self;
}

/** 判断是否可以响应Selector */
- (BOOL)respondsToSelector:(SEL)aSelector {
    return self.canRespondBlock(aSelector);
}

/** 解析方法签名 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    NSString *encoding = self.searchSignatureBlock(selector);
    return [NSMethodSignature signatureWithObjCTypes:encoding.UTF8String];
}

/** 消息转发 */
- (void)forwardInvocation:(NSInvocation *)invocation {
    self.forwardInvocationBlock(invocation);
}

- (void)invokeAPIWithInvocation:(id)invocation
                   blockArgName:(NSString *)blockArgName
                  blockArgIndex:(NSInteger)blockArgIndex
                   blockArgInfo:(NSArray<ObjCInvokeParamInfo *> * _Nullable)blockArgInfo {
    NSInvocation *objcInvocation = (NSInvocation *)invocation;
    if (blockArgInfo.count > 0) {
        NSString* sender = NSStringFromSelector(objcInvocation.selector);
        NSMutableString *blockSignature = [[NSMutableString alloc] initWithCapacity:32];
        for (ObjCInvokeParamInfo *param in blockArgInfo) {
            if (param.canUseVoidPointer) {
                [blockSignature appendString:@"v"];
            } else  {
                NSLog(@">>> TODO: support.");
            }
        }
        
        ParseArgValueBlock parseBlock = ^id(void *value, BOOL isRefType, NSString *declType) {
            if (isRefType) {
                return (__bridge id)value;
            } else {
                if ([declType isEqualToString:@"NSInteger"] || [declType isEqualToString:@"int"]) {
                    union int_cast cast;
                    cast.ptr = value;
                    return @(cast.value);
                } else if ([declType isEqualToString:@"NSUInteger"]) {
                    union uint_cast cast;
                    cast.ptr = value;
                    return @(cast.value);
                } else if ([declType isEqualToString:@"BOOL"] || [declType isEqualToString:@"bool"]) {
                    union bool_cast cast;
                    cast.ptr = value;
                    return @(cast.value);
                } else {
                    NSLog(@">>> TODO: support.");
                }
            }
            return nil;
        };

        id blockArg = nil;
        if ([blockSignature isEqualToString:@"v"]) {
            BOOL arg1RefType = blockArgInfo[0].isObjCRefType;
            NSString *arg1Type = blockArgInfo[0].declType;
            blockArg = ^(void *arg1) {
                id arg1Value = parseBlock(arg1, arg1RefType, arg1Type);
                ((void(*)(id, SEL, id, id))objc_msgSend)(self, NSSelectorFromString(blockArgName), sender, arg1Value);
            };
        } else if ([blockSignature isEqualToString:@"vv"]) {
            BOOL arg1RefType = blockArgInfo[0].isObjCRefType;
            NSString *arg1Type = blockArgInfo[0].declType;
            BOOL arg2RefType = blockArgInfo[1].isObjCRefType;
            NSString *arg2Type = blockArgInfo[1].declType;
            blockArg = ^(void *arg1, void *arg2) {
                id arg1Value = parseBlock(arg1, arg1RefType, arg1Type);
                id arg2Value = parseBlock(arg2, arg2RefType, arg2Type);
                ((void(*)(id, SEL, id, id, id))objc_msgSend)(self, NSSelectorFromString(blockArgName), sender, arg1Value, arg2Value);
            };
        } else if ([blockSignature isEqualToString:@"vvv"]) {
            BOOL arg1RefType = blockArgInfo[0].isObjCRefType;
            NSString *arg1Type = blockArgInfo[0].declType;
            BOOL arg2RefType = blockArgInfo[1].isObjCRefType;
            NSString *arg2Type = blockArgInfo[1].declType;
            BOOL arg3RefType = blockArgInfo[2].isObjCRefType;
            NSString *arg3Type = blockArgInfo[2].declType;
            blockArg = ^void(void *arg1, void * arg2, void * arg3) {
                id arg1Value = parseBlock(arg1, arg1RefType, arg1Type);
                id arg2Value = parseBlock(arg2, arg2RefType, arg2Type);
                id arg3Value = parseBlock(arg3, arg3RefType, arg3Type);
                ((void(*)(id, SEL, id, id, id, id))objc_msgSend)(self, NSSelectorFromString(blockArgName), sender, arg1Value, arg2Value, arg3Value);
            };
        } else if ([blockSignature isEqualToString:@"vvvv"]) {
            BOOL arg1RefType = blockArgInfo[0].isObjCRefType;
            NSString *arg1Type = blockArgInfo[0].declType;
            BOOL arg2RefType = blockArgInfo[1].isObjCRefType;
            NSString *arg2Type = blockArgInfo[1].declType;
            BOOL arg3RefType = blockArgInfo[2].isObjCRefType;
            NSString *arg3Type = blockArgInfo[2].declType;
            BOOL arg4RefType = blockArgInfo[3].isObjCRefType;
            NSString *arg4Type = blockArgInfo[3].declType;
            blockArg = ^(void *arg1, void *arg2, void *arg3, void *arg4) {
                id arg1Value = parseBlock(arg1, arg1RefType, arg1Type);
                id arg2Value = parseBlock(arg2, arg2RefType, arg2Type);
                id arg3Value = parseBlock(arg3, arg3RefType, arg3Type);
                id arg4Value = parseBlock(arg4, arg4RefType, arg4Type);
                ((void(*)(id, SEL, id, id, id, id, id))objc_msgSend)(self, NSSelectorFromString(blockArgName), sender, arg1Value, arg2Value, arg3Value, arg4Value);
            };
        } else if ([blockSignature isEqualToString:@"vvvvv"]) {
            BOOL arg1RefType = blockArgInfo[0].isObjCRefType;
            NSString *arg1Type = blockArgInfo[0].declType;
            BOOL arg2RefType = blockArgInfo[1].isObjCRefType;
            NSString *arg2Type = blockArgInfo[1].declType;
            BOOL arg3RefType = blockArgInfo[2].isObjCRefType;
            NSString *arg3Type = blockArgInfo[2].declType;
            BOOL arg4RefType = blockArgInfo[3].isObjCRefType;
            NSString *arg4Type = blockArgInfo[3].declType;
            BOOL arg5RefType = blockArgInfo[4].isObjCRefType;
            NSString *arg5Type = blockArgInfo[4].declType;
            blockArg = ^(void *arg1, void *arg2, void *arg3, void *arg4, void *arg5) {
                id arg1Value = parseBlock(arg1, arg1RefType, arg1Type);
                id arg2Value = parseBlock(arg2, arg2RefType, arg2Type);
                id arg3Value = parseBlock(arg3, arg3RefType, arg3Type);
                id arg4Value = parseBlock(arg4, arg4RefType, arg4Type);
                id arg5Value = parseBlock(arg5, arg5RefType, arg5Type);
                ((void(*)(id, SEL, id, id, id, id, id, id))objc_msgSend)(self, NSSelectorFromString(blockArgName), sender, arg1Value, arg2Value, arg3Value, arg4Value, arg5Value);
            };
        } else if ([blockSignature isEqualToString:@"vvvvvv"]) {
            BOOL arg1RefType = blockArgInfo[0].isObjCRefType;
            NSString *arg1Type = blockArgInfo[0].declType;
            BOOL arg2RefType = blockArgInfo[1].isObjCRefType;
            NSString *arg2Type = blockArgInfo[1].declType;
            BOOL arg3RefType = blockArgInfo[2].isObjCRefType;
            NSString *arg3Type = blockArgInfo[2].declType;
            BOOL arg4RefType = blockArgInfo[3].isObjCRefType;
            NSString *arg4Type = blockArgInfo[3].declType;
            BOOL arg5RefType = blockArgInfo[4].isObjCRefType;
            NSString *arg5Type = blockArgInfo[4].declType;
            BOOL arg6RefType = blockArgInfo[5].isObjCRefType;
            NSString *arg6Type = blockArgInfo[5].declType;
            blockArg = ^(void *arg1, void *arg2, void *arg3, void *arg4, void *arg5, void *arg6) {
                id arg1Value = parseBlock(arg1, arg1RefType, arg1Type);
                id arg2Value = parseBlock(arg2, arg2RefType, arg2Type);
                id arg3Value = parseBlock(arg3, arg3RefType, arg3Type);
                id arg4Value = parseBlock(arg4, arg4RefType, arg4Type);
                id arg5Value = parseBlock(arg5, arg5RefType, arg5Type);
                id arg6Value = parseBlock(arg6, arg6RefType, arg6Type);
                ((void(*)(id, SEL, id, id, id, id, id, id, id))objc_msgSend)(self, NSSelectorFromString(blockArgName), sender, arg1Value, arg2Value,
                             arg3Value, arg4Value, arg5Value, arg6Value);
            };
        }
        [objcInvocation setArgument:&blockArg atIndex:blockArgIndex];
        [objcInvocation retainArguments];
    }
    [objcInvocation invoke];
}

@end

@implementation ObjCInvokeParamInfo

- (BOOL)canUseVoidPointer {
    if (self.isObjCRefType) {
        return YES;
    } else if ([self.declType isEqualToString:@"NSInteger"]
               || [self.declType isEqualToString:@"NSUInteger"]
               || [self.declType isEqualToString:@"int"]
               || [self.declType isEqualToString:@"BOOL"]
               || [self.declType isEqualToString:@"bool"]) {
        return YES;
    }
    return NO;
}

@end

id getObjValueByKeyPath(NSString * keyPath, NSObject *obj) {
    return [obj valueForKeyPath:keyPath];
}
